package com.chinaums.xm.wechat.util;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

/**
 * Created on 2015/7/1.
 *
 * @author YaoFeng
 */
public class IdCardValidator {

    public enum ValidateResult {

        success("验证通过"),
        lengthInvalid("身份证号码长度应该为15位或18位。"),
        isNotNumber("身份证15位号码都应为数字 ; 18位号码除最后一位外，都应为数字。"),
        birthDayInvalid("身份证生日无效。"),
        outOfDate("身份证生日不在有效范围。"),
        provinceInvalid("身份证地区编码错误。"),
        verifyCodeError("身份证无效，不是合法的身份证号码");

        public final String info;

        ValidateResult(final String info) {
            this.info = info;
        }

        public boolean isSuccess() {
            return this == success;
        }
    }

    private static final char[] verifyCodeArr = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
    private static final int[] wi = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
    private static final Map<String, String> provinceCodeMap = new ConcurrentHashMap<>();

    static {
        provinceCodeMap.put("11", "北京");
        provinceCodeMap.put("12", "天津");
        provinceCodeMap.put("13", "河北");
        provinceCodeMap.put("14", "山西");
        provinceCodeMap.put("15", "内蒙古");
        provinceCodeMap.put("21", "辽宁");
        provinceCodeMap.put("22", "吉林");
        provinceCodeMap.put("23", "黑龙江");
        provinceCodeMap.put("31", "上海");
        provinceCodeMap.put("32", "江苏");
        provinceCodeMap.put("33", "浙江");
        provinceCodeMap.put("34", "安徽");
        provinceCodeMap.put("35", "福建");
        provinceCodeMap.put("36", "江西");
        provinceCodeMap.put("37", "山东");
        provinceCodeMap.put("41", "河南");
        provinceCodeMap.put("42", "湖北");
        provinceCodeMap.put("43", "湖南");
        provinceCodeMap.put("44", "广东");
        provinceCodeMap.put("45", "广西");
        provinceCodeMap.put("46", "海南");
        provinceCodeMap.put("50", "重庆");
        provinceCodeMap.put("51", "四川");
        provinceCodeMap.put("52", "贵州");
        provinceCodeMap.put("53", "云南");
        provinceCodeMap.put("54", "西藏");
        provinceCodeMap.put("61", "陕西");
        provinceCodeMap.put("62", "甘肃");
        provinceCodeMap.put("63", "青海");
        provinceCodeMap.put("64", "宁夏");
        provinceCodeMap.put("65", "新疆");
        provinceCodeMap.put("71", "台湾");
        provinceCodeMap.put("81", "香港");
        provinceCodeMap.put("82", "澳门");
        provinceCodeMap.put("91", "国外");
    }

    /**
     * 身份证的有效验证
     *
     * @param idCardNo 身份证号
     * @return 有效：返回"" 无效：返回String信息
     */
    public static ValidateResult validate(String idCardNo) {
        if (idCardNo.length() != 15 && idCardNo.length() != 18) { // 号码的长度 15位或18位
            return ValidateResult.lengthInvalid;
        }
        final String idCardNo17 = idCardNo.length() == 18 ? idCardNo.substring(0, 17) : idCardNo.substring(0, 6) + "19" + idCardNo.substring(6, 15);
        if (!isNumeric(idCardNo17)) {
            return ValidateResult.isNotNumber;
        }

        final LocalDate birthday; // 生日
        try { // 出生年月是否有效
            birthday = LocalDate.parse(idCardNo17.substring(6, 14), DateTimeFormatter.ofPattern("yyyyMMdd"));
        } catch (DateTimeParseException e) {
            return ValidateResult.birthDayInvalid;
        }

        if (birthday.isBefore(LocalDate.now().minusYears(150)) || birthday.isAfter(LocalDate.now())) {
            return ValidateResult.outOfDate;
        }

        final String provinceCode = idCardNo17.substring(0, 2);
        if (!provinceCodeMap.containsKey(provinceCode)) { // 校验省份代码有效性
            return ValidateResult.provinceInvalid;
        }

        if (idCardNo.length() == 18) { // 判断校验位
            int totalMulAiWi = 0;
            for (int i = 0; i < 17; i++) {
                totalMulAiWi += Integer.parseInt(String.valueOf(idCardNo17.charAt(i))) * wi[i];
            }
            if (idCardNo.charAt(17) != verifyCodeArr[totalMulAiWi % 11]) {
                return ValidateResult.verifyCodeError;
            }
        }

        return ValidateResult.success;
    }

    private static boolean isNumeric(String str) {
        return Pattern.compile("[0-9]*").matcher(str).matches();
    }
}
